"""游戏元素模块"""
import pygame
import random

SCREEN_RECT = pygame.Rect(0, 0, 480, 700)  # 游戏窗口位置，大小
FRAME_INTERVAL = 10  # 逐帧动画间隔

HERO_BOMB_COUNT = 3  # 默认炸弹数量
HERO_DEFAULT_MID_BOTTOM = (SCREEN_RECT.centerx,
                           SCREEN_RECT.bottom - 90)  # 英雄默认位置
HERO_DEAD_EVENT = pygame.USEREVENT  # 玩家死亡事件事件
HERO_POWER_OFF_EVENT = pygame.USEREVENT + 1  # 取消玩家无敌事件
HERO_FIRE_EVENT = pygame.USEREVENT + 2  # 玩家发射子弹事件
THROW_SUPPLY_EVENT = pygame.USEREVENT + 3  # 投放道具事件
BULLET_ENHANCED_OFF_EVENT = pygame.USEREVENT + 4  # 关闭子弹增强事件


class GameSprite(pygame.sprite.Sprite):
    res_path = 'res/images/'

    def __init__(self, image_name, speed, *groups):
        """初始化精灵对象"""
        # 调用父类方法，把当前精灵对象放到精灵组
        super().__init__(*groups)

        # 创建图片
        self.image = pygame.image.load(self.res_path + image_name)

        # 获取矩阵
        self.rect = self.image.get_rect()

        # 设置移动速度
        self.speed = speed

        # 生成图片内容轮廓，提高像素块碰撞检测效率
        self.mask = pygame.mask.from_surface(self.image)

    def update(self, *args):
        """更新元素数据"""
        # 更改y坐标实现动态
        self.rect.y += self.speed


class Background(GameSprite):
    """背景类"""

    def __init__(self, is_alt, *groups):
        """如果is_alt为true，则初始化时这个精灵显示在窗口上方外部，false则显示在窗口内部"""
        super(Background, self).__init__('background.png', 1, *groups)

        if is_alt:
            self.rect.y = -self.rect.h

    def update(self, *args):
        super(Background, self).update(*args)

        # 如果背景图片已经滚动到窗口底部，则立即回到最上面重新显示
        if self.rect.y > self.rect.h:
            self.rect.y = -self.rect.y


class StatusButton(GameSprite):

    def __init__(self, image_names, *groups):
        """image_names接收一个元组，元组的0下标必须是暂停的图片，1下标必须是运行的图片"""
        super(StatusButton, self).__init__(image_names[0], 0, *groups)

        # 准备用于切换显示的两张图片
        self.images = [pygame.image.load(self.res_path + name) for name in image_names]

    def switch_status(self, is_pause):
        """根据是否暂停，切换要使用的图片对象"""
        self.image = self.images[1 if is_pause else 0]


class Label(pygame.sprite.Sprite):
    """标签精灵类"""

    # 字体路径
    font_path = 'res/font/MarkerFelt.ttc'

    def __init__(self, text, size, color, *groups):
        """初始化标签精灵的数据"""
        super(Label, self).__init__(*groups)

        # 创建字体对象
        self.font = pygame.font.Font(self.font_path, size)

        # 字体颜色
        self.color = color

        # 精灵属性
        self.image = self.font.render(text, True, self.color)  # 渲染文字
        self.rect = self.image.get_rect()  # 位置

    def set_text(self, text):
        """更新显示的文本内容"""
        self.image = self.font.render(text, True, self.color)  # 渲染文字
        self.rect = self.image.get_rect()  # 位置


class Plane(GameSprite):
    """飞机精灵类"""

    def __init__(self, normal_names, speed, hp, value, wav_name, hurt_name, destroy_name, *groups):
        """飞机类初始化"""
        super(Plane, self).__init__(normal_names[0], speed, *groups)
        # 飞机基本属性
        self.hp = hp  # 当前生命值
        self.max_hp = hp  # 初始生命值
        self.value = value  # 分值
        self.wav_name = wav_name  # 音效名

        self.normal_images = [pygame.image.load(self.res_path + name) for name in normal_names]  # 正常状态图片列表
        self.normal_index = 0  # 正常状态图片索引
        self.hurt_image = pygame.image.load(self.res_path + hurt_name)  # 受伤图像
        self.destroy_images = [pygame.image.load(self.res_path + name) for name in destroy_name]  # 摧毁状态图像列表
        self.destroy_index = 0  # 摧毁状态索引

    def reset_plane(self):
        """重置飞机"""
        self.hp = self.max_hp

        self.normal_index = 0
        self.destroy_index = 0

        self.image = self.normal_images[0]

    def update(self, *args):
        """更新状态，准备下一次要显示的内容"""
        # 判断是否需要更新
        if not args[0]:
            return

        if self.hp == self.max_hp:
            # 切换要显示的图片
            self.image = self.normal_images[self.normal_index]

            # 计算下一个显示的索引
            count = len(self.normal_images)  # 计算图片个数
            self.normal_index = (self.normal_index + 1) % count  # 防止索引越界
        elif self.hp > 0:
            # 受伤
            self.image = self.hurt_image

        else:
            # 死亡
            if self.destroy_index < len(self.destroy_images):
                self.image = self.destroy_images[self.destroy_index]

                self.destroy_index += 1

            else:
                self.reset_plane()


class Enemy(Plane):
    """敌人飞机"""

    def __init__(self, kind, max_speed, *groups):
        """初始化敌人飞机"""
        self.kind = kind
        self.max_speed = max_speed

        if kind == 0:
            # 小敌机
            super(Enemy, self).__init__(
                ['enemy1.png'], 1, 1, 1000, 'enemy1_down.wav', 'enemy1.png',
                ['enemy1_down%d.png' % i for i in range(1, 5)],
                *groups
            )
        elif kind == 1:
            # 中敌机
            super(Enemy, self).__init__(
                ['enemy2.png'], 1, 6, 6000, 'enemy2_down.wav', 'enemy2_hit.png',
                ['enemy2_down%d.png' % i for i in range(1, 5)],
                *groups
            )
        elif kind == 2:
            # 大敌机
            super(Enemy, self).__init__(
                ['enemy3_n1.png', 'enemy3_n2.png'], 1, 15, 15000, 'enemy3_down.wav', 'enemy3_hit.png',
                ['enemy3_down%d.png' % i for i in range(1, 7)],
                *groups
            )

        # 初始化飞机时，飞机随机位置出现
        self.reset_plane()

    def reset_plane(self):
        """重置敌人飞机"""

        super(Enemy, self).reset_plane()

        # 敌人飞机的数据重置
        x = random.randint(0, SCREEN_RECT.w - self.rect.w)
        y = random.randint(0, SCREEN_RECT.h - self.rect.h) - SCREEN_RECT.h

        self.rect.topleft = (x, y)

        # 重置飞机速度
        self.speed = random.randint(1, self.max_speed)

    def update(self, *args):
        """更新飞机的位置信息"""

        super(Enemy, self).update(*args)

        # 根据血量判断是否还要移动，死了就不用了
        if self.hp > 0:
            self.rect.y += self.speed

        # 如果飞机已经移动到界面以外则重置飞机
        if self.rect.y >= SCREEN_RECT.h:
            self.reset_plane()


class Hero(Plane):
    """英雄飞机"""

    def __init__(self, *groups):
        """初始化游戏飞机"""
        self.is_power = False  # 是否无敌
        self.bomb_cout = HERO_BOMB_COUNT  # 炸弹数量
        self.bullets_kind = 0  # 子弹类型
        self.bullets_group = pygame.sprite.Group()  # 子弹类型精灵组

        super(Hero, self).__init__(('me1.png', 'me2.png'),
                                   5, 1, 0, 'me_down.wav', 'me1.png',
                                   ['me_destroy_%d.png' % x for x in range(1, 5)],
                                   *groups)  # 飞机图片，速度，分值，死亡声音，被击中的图片，爆炸过程图片

        self.rect.midbottom = HERO_DEFAULT_MID_BOTTOM  # 设置飞机位置为屏幕中间

        pygame.time.set_timer(HERO_FIRE_EVENT, 100)  # 创建玩家飞机后每0.1秒发射一次子弹

    def update(self, *args):
        """
        args的0号下标说明是否要跟心下一帧，1号说明玩家飞机水平移动方向基数，2号说明玩家飞机垂直方向的移动基数
        """

        super(Hero, self).update(*args)

        if len(args) != 3 or self.hp <= 0:
            return

        self.rect.x += args[1] * self.speed

        # 修正屏幕边缘问题
        # 左右
        self.rect.x = 0 if self.rect.x < 0 else self.rect.x
        if self.rect.right > SCREEN_RECT.right:
            self.rect.right = SCREEN_RECT.right

        self.rect.y += args[2] * self.speed

        # 上下
        self.rect.y = 0 if self.rect.y < 0 else self.rect.y
        if self.rect.bottom > SCREEN_RECT.bottom:
            self.rect.bottom = SCREEN_RECT.bottom

    def blowup(self, enemies_group):
        """炸毁所有敌机"""
        # 判断是否能炸
        if self.bomb_cout <= 0 or self.hp <= 0:
            return 0

        # 爆炸后计算得分
        self.bomb_cout -= 1
        score = 0
        count = 0

        for enemy in enemies_group.sprites():
            if enemy.rect.bottom > 0:
                score += enemy.value
                enemy.hp = 0
                count += 1

        return score

    def reset_plane(self):
        """重置玩家飞机"""
        super(Hero, self).reset_plane()

        self.is_power = True
        self.bomb_cout = HERO_BOMB_COUNT
        self.bullets_kind = 0

        # 发布事件，更新面板
        pygame.event.post(pygame.event.Event(HERO_DEAD_EVENT))

        # 发布定时事件,3秒后激活
        pygame.time.set_timer(HERO_POWER_OFF_EVENT, 3000)

    def fire(self, display_group):
        """发射子弹

        :param display_group: 要添加的显示精灵组
        """
        # 需要将子弹精灵添加到两个精灵组
        groups = (self.bullets_group, display_group)

        # 测试子弹增强效果
        # self.bullets_kind = 1

        for i in range(3):
            # 创建子弹精灵
            bullet1 = Bullet(self.bullets_kind, *groups)

            # 计算子弹的垂直位置
            y = self.rect.y - i * 15

            # # 判断子弹类型
            if self.bullets_kind == 0:
                bullet1.rect.midbottom = (self.rect.centerx, y)
            else:
                bullet1.rect.midbottom = (self.rect.centerx - 20, y)

                # 再创建一颗子弹
                bullet2 = Bullet(self.bullets_kind, *groups)
                bullet2.rect.midbottom = (self.rect.centerx + 20, y)


class Bullet(GameSprite):
    """子弹类"""

    def __init__(self, kind, *groups):
        """初始化方法

        :param kind: 子弹类型
        :param groups: 要添加到的精灵组
        """

        image_name = "bullet1.png" if kind == 0 else "bullet2.png"
        super().__init__(image_name, -12, *groups)

        self.damage = 1  # 杀伤力

    def update(self, *args):
        super().update(*args)  # 向上移动

        # 判断是否从上方飞出窗口
        if self.rect.bottom < 0:
            self.kill()


class Supply(GameSprite):
    """道具精灵"""

    def __init__(self, kind, *groups):
        """初始化道具属性"""

        image_name = 'bomb_supply.png' if kind == 0 else 'bullet_supply.png'
        super(Supply, self).__init__(image_name, 3, *groups)

        # 道具类型
        self.kind = kind

        # 音效
        self.wav_name = 'get_bomb.wav' if kind == 0 else 'get_bullet.wav'

        # 初始位置
        self.rect.bottom = SCREEN_RECT.h

    def update(self, *args):
        """修改道具位置"""

        # 移动到屏幕之外则停止
        if self.rect.y > SCREEN_RECT.h:
            return

        super(Supply, self).update(*args)

    def throw_supply(self):
        """投放道具"""
        self.rect.bottom = 0  # 窗口底部
        self.rect.x = random.randint(0, SCREEN_RECT.w - self.rect.w)